package com.ficus.road.maintain.util.queue;

import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LogMonitorCounter {
    //日志监控计数器
    static private LogMonitorCounter sLogMonitorCounter =
            new LogMonitorCounter();
    static private int outputIntervalInMs = 10000;
    private Map<String, Counter> counterMap;
    private ReentrantLock counterMapLock;

    //构造器私有，外部不能通过new来创建
    private LogMonitorCounter() {
        counterMap = new TreeMap<>();
        counterMapLock = new ReentrantLock();
        (new Thread(() -> this.Loop())).start();
    }

    static public Counter getCounter(String key, CounterType counterType) {
        sLogMonitorCounter.counterMapLock.lock();
        Map<String, Counter> counterMap = sLogMonitorCounter.counterMap;
        Counter counter = null;
        try {
            if (!(counterMap.containsKey(key))) {
                counterMap.put(key, counterType.getCounter());
            }
            counter = counterMap.get(key);
        } finally {
            sLogMonitorCounter.counterMapLock.unlock();
        }
        return counter;
    }

    private void Loop() {
        while (true) {
            int counterSize = 0;
            counterMapLock.lock();
            try {
                counterSize = counterMap.size();
            } finally {
                counterMapLock.unlock();
            }

            final List<SnapShot> counterSnapShots =
                    new ArrayList<>(2 * counterSize);

            counterMapLock.lock();
            try {
                counterMap.forEach((key, counter) -> {
                    SnapShot snapShot = counter.Reset();
                    snapShot.key = key;
                    counterSnapShots.add(snapShot);
                });
            } finally {
                counterMapLock.unlock();
            }

            Iterator<SnapShot> snapShotIterator =
                    counterSnapShots.iterator();
            while (snapShotIterator.hasNext()) {
                snapShotIterator.next().print();
            }
            try {
                Thread.sleep(outputIntervalInMs);
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    /**
     * Counter类型
     */
    public enum CounterType {
        QPS(QpsCounter.class),
        VALUE(ValueCounter.class),
        ACCUMULATE(AccumulateCounter.class);
        private Class<? extends Counter> clazz;

        CounterType(Class<? extends Counter> clazz) {
            this.clazz = clazz;
        }

        public Counter getCounter() {
            try {
                Counter counter = clazz.newInstance();
                counter.init();
                return counter;
            } catch (Exception e) {
                log.error("getCounter error:", e);
                return null;
            }
        }
    }

    static public class Counter {
        private ReentrantLock lock;

        private Counter() {
            this.lock = new ReentrantLock();
        }

        private void init() {
            lock.lock();
            try {
                doInit();
            } finally {
                lock.unlock();
            }
        }

        void doInit() {

        }

        /**
         * 添加计数
         *
         * @param number
         */
        public void addCount(long number) {
            lock.lock();
            try {
                doAddCount(number);
            } finally {
                lock.unlock();
            }
        }

        void doAddCount(long number) {

        }

        /**
         * 重置，并返回快照
         *
         * @return
         */
        SnapShot Reset() {
            lock.lock();
            try {
                return doReset();
            } finally {
                lock.unlock();
            }
        }

        SnapShot doReset() {
            return null;
        }
    }

    static class QpsCounter extends Counter {

        private long startTime;
        private long count;
        private long sum;

        @Override
        public void doInit() {
            this.startTime = System.currentTimeMillis();
            this.count = 0;
            this.sum = 0;
        }

        @Override
        public void doAddCount(long number) {
            sum += number;
            if (number > 0) {
                count += number;
            }
        }

        @Override
        public SnapShot doReset() {
            QpsCounterSnapShot snapShot = new QpsCounterSnapShot();
            snapShot.startTime = this.startTime;
            snapShot.count = this.count;
            snapShot.sum = this.sum;
            snapShot.endTime = System.currentTimeMillis();
            this.startTime = snapShot.endTime;
            this.count = 0;
            this.sum = 0;
            return snapShot;
        }

    }

    static class ValueCounter extends Counter {

        private long min;
        private long max;
        private long count;
        private long sum;

        @Override
        public void doInit() {
            this.min = Long.MAX_VALUE;
            this.max = Long.MIN_VALUE;
            this.count = 0;
            this.sum = 0;
        }

        @Override
        public void doAddCount(long number) {
            sum += number;
            count += 1;
            if (number < min) {
                min = number;
            }
            if (number > max) {
                max = number;
            }
        }

        @Override
        public SnapShot doReset() {
            ValueCounterSnapShot snapShot = new ValueCounterSnapShot();
            snapShot.min = this.min;
            snapShot.max = this.max;
            snapShot.count = this.count;
            snapShot.sum = this.sum;
            this.min = Long.MAX_VALUE;
            this.max = Long.MIN_VALUE;
            this.count = 0;
            this.sum = 0;
            return snapShot;
        }
    }

    static class AccumulateCounter extends Counter {

        private long min;
        private long max;
        private long count;
        private long sum;
        private long value;

        @Override
        public void doInit() {
            this.min = Long.MAX_VALUE;
            this.max = Long.MIN_VALUE;
            this.count = 0;
            this.sum = 0;
            this.value = 0;
        }

        @Override
        public void doAddCount(long delta) {
            value += delta;
            long number = value;
            sum += number;
            count += 1;
            if (number < min) {
                min = number;
            }
            if (number > max) {
                max = number;
            }
        }

        @Override
        public SnapShot doReset() {
            AccumulateCounterSnapShot snapShot = new AccumulateCounterSnapShot();
            snapShot.min = this.min;
            snapShot.max = this.max;
            snapShot.count = this.count;
            snapShot.sum = this.sum;
            snapShot.value = this.value;
            this.min = this.value;
            this.max = this.value;
            this.count = 0;
            this.sum = 0;
            return snapShot;
        }
    }

    static private class QpsCounterSnapShot extends SnapShot {
        long startTime;
        long endTime;
        long count;
        long sum;

        @Override
        public void print() {
            double qps = 0.0;
            if (this.count == 0) {
                qps = 0.0;
            } else {
                long durationInMs = this.endTime - this.startTime;
                if (durationInMs <= 0) {
                    qps = 0.0;
                } else {
                    qps = (double) (this.count * 1000) / (double) (durationInMs);
                }
            }

            log.info("QPS: {}:{}", this.key, qps);
        }
    }

    static private class ValueCounterSnapShot extends SnapShot {

        private long min;
        private long max;
        private long count;
        private long sum;

        @Override
        public void print() {
            float avg = 0.0F;
            if (count != 0) {
                avg = sum / (count * 1.0F);
            } else {
                min = 0;
                max = 0;
            }
            log.warn(
                    "{}: avg:{} min:{} max:{} count:{}",
                    this.key, avg, min, max, count);
        }
    }

    static private class AccumulateCounterSnapShot extends SnapShot {

        private long min;
        private long max;
        private long count;
        private long sum;
        private long value;

        @Override
        public void print() {
            float avg = 0.0F;
            if (count != 0) {
                avg = sum / (count * 1.0F);
            } else {
                min = value;
                max = value;
                avg = value;
            }
            log.warn(
                    "{}: avg:{} min:{} max:{} count:{}",
                    this.key, avg, min, max, count);
        }
    }

    static private class SnapShot {
        String key;

        public void print() {

        }
    }

}
